import socket, uuid, const, logging, config, datetime, threading, logger

class Path(object):     
    def __init__(self, id, src_id, next_jondo, is_last = False):
        self.id = id
        self.src_id = src_id        
        self.next_jondo = next_jondo
        self.is_last = is_last         
                
class PathManager(object):
    __paths = {}
    lock = threading.RLock()
    logger = logger.setup_logging("PathManager", config.LOG_PATHS)
    
    def __connect_to(self, sock, host_port):
        try: sock.connect(host_port)        
        except socket.error:
            return 0
        return 1
    
    def __create_id(self):        
        return str(uuid.uuid1())
    
    def connect_to_dest(self, netloc, sock, headers):        
        i = netloc.find(':')
        if i >= 0:
            host_port = netloc[:i], int(netloc[i+1:])
        else:
            host_port = netloc, 80
        del headers[const.SOURCE_PATH_ID_HEADER]        
        i = 0
        while i < const.MAX_CONN_RETRY:
            if self.__connect_to(sock, host_port):
                return sock
            else:
                i += 1
        return None
                    
    def find_path(self, path):
        return self.__paths[path.id]
    
    def show_paths(self):
        i = 1
        with self.lock:
            for id , path in self.__paths.items():
                print "\nPath #%s" % i
                print "Path id: %s" % id
                print "Path src_id: %s" % path.src_id
                if path.next_jondo:
                    print "Path next_jondo: %s : %s" % path.next_jondo.host_port
                print "Is last node: %s " % path.is_last
                print "\n"
                i+=1
            return None
         
    def translate_path(self, src_id):
        for id , path in self.__paths.items():
            if path.src_id == src_id:
                return path                
        return None
     
    def reset_paths(self):
        with self.lock:
            self.__paths.clear()
    
    def follow_through(self, path, sock, headers):
        host_port = path.next_jondo.host_port
        headers[const.SOURCE_PATH_ID_HEADER] = path.id            
        i = 0
        while i < const.MAX_CONN_RETRY:
            if self.__connect_to(sock, host_port):
                return path.next_jondo
            else:
                i += 1
        return None    
    
    def build_path(self, crowd, sock, src_id, headers, last = False):
        """Build a new path connected to another jondo or 
        to the final destination.
        
        arguments:
        crowd -- the crowd on which we must pick a random jondo
        sock -- the socket we have to connect
        src_id -- the path source id (if the request was forwarded)
        headers -- the request headers (we must include our path id in it)
        last -- check if this jondo is the last before the end server
        
        return: 
        path -- the path (if error occurs: None) 
        jondo -- the jondo we pick (if exists) (if error occurs: None)        
        """
    
        i = 0
        jondo = None
        id = self.__create_id()
        if not last:
            jondo = crowd.pick_random_jondo()
            if jondo:
                while i < const.MAX_CONN_RETRY and jondo:
                    if self.__connect_to(sock, jondo.host_port) != 1:
                        jondo = crowd.pick_random_jondo()
                        i += 1  
                    else:
                        break        
            if i >= const.MAX_CONN_RETRY or not jondo: 
                return (None, None)  # network issue or no available jondos...
        with self.lock:
            path = Path(id, src_id, jondo, last)
            self.__paths[id] = path
            headers[const.SOURCE_PATH_ID_HEADER] = path.id
            if not last:
                self.logger.debug("Source path id: '" + str(src_id) + "' has new path: '" 
                              + path.id + "' to " + str(jondo.host_port))
            else:
                self.logger.debug("Source path id: '" + str(src_id) + "' has new path: '" 
                              + path.id + "' to destination")
            return (path, jondo)
